////////////////////////////////////////////////////////////////////////////////
//Copyright 2009 Christopher Betancourt
//
//This file is part of PEAQ
//
//PEAQ is free software: you can redistribute it and/or modify it under the 
//terms of the GNU Lesser General Public License as published by the Free 
//Software Foundation, either version 3 of the License, or (at your option) any
//later version.
//
//PEAQ is distributed in the hope that it will be useful, but WITHOUT ANY 
//WARRANTY; without even the implied warranty of MERCHANTABLILTY or FITTNESS FOR
//A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
//details.
//
//You should have received a copy of the GNU Lesser Public License with PEAQ.  
//If not, see <http://www.gnu.org/licenses/>.
////////////////////////////////////////////////////////////////////////////////
package extractBlack;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;

import processing.*;

public class ExtractBlackModule implements ImageProcessor, StatisticsGenerator{

	public int[][] extractPattern(BufferedImage image, ProcessingParams params) {
		ExtractBlackParams ebParams=(ExtractBlackParams) params;
		ExtractBlackStatistics stats=ebParams.getStats();
		int pixWidth=ebParams.getPixWidth();
		int pixHeight=ebParams.getPixHeight();
		double stdDevs=ebParams.getStdDevs();
		int [][] retVal=new int[image.getHeight()/pixHeight+1][image.getWidth()/pixWidth+1];
		double [][] vals=new double[image.getHeight()][image.getWidth()];
		int rgbComponents[]=new int[3];
		float hsbComponents[]=new float[3];
		ColorModel cm=image.getColorModel();
		double valAccum=0.0;
		int xNew=-1; //initialized to -1 to compensate for the double count of 0
		int yNew=-1; //initialized to -1 to compensate for the double count of 0
		double mean;
		double stdDev;
		
		//initialize the arrays
		for(int y=0;y<retVal.length;++y)
		{
			for(int x=0; x<retVal[y].length;++x)
			{
				retVal[y][x]=0;
			}
		}
		
		if(stats==null)
		{
			//if the statistics weren't provided to us, then we need to compute
			//them.
			
			//compute mean
			for(int y=0; y<image.getHeight();++y)
			{
				for(int x=0; x<image.getWidth();++x)
				{
					cm.getComponents(image.getRGB(x, y),rgbComponents,0);
					Color.RGBtoHSB(rgbComponents[0], rgbComponents[1],
							rgbComponents[2], hsbComponents);
					valAccum+=hsbComponents[2];
					vals[y][x]=hsbComponents[2];
				}
			}
			mean=valAccum/(image.getHeight()*image.getWidth());
			valAccum=0;
			
			//compute standard deviation
			for(int y=0; y<image.getHeight();++y)
				for(int x=0; x<image.getWidth();++x)
				{
					valAccum+=(mean-vals[y][x])*(mean-vals[y][x]);
				}
			stdDev=Math.sqrt(valAccum/(image.getWidth()*image.getHeight()));
		}
		else
		{
			mean=stats.getMean();
			stdDev=stats.getStdDev();
			for(int y=0; y<image.getHeight();++y)
			{
				for(int x=0; x<image.getWidth();++x)
				{
					cm.getComponents(image.getRGB(x, y),rgbComponents,0);
					Color.RGBtoHSB(rgbComponents[0], rgbComponents[1],
							rgbComponents[2], hsbComponents);
					vals[y][x]=hsbComponents[2];
				}
			}
		}
		
		
		//now, find the black areas
		for(int y=0; y<image.getHeight();++y)
		{
			if(y%pixHeight==0)
				++yNew;
			for(int x=0; x<image.getWidth();++x)
			{
				if(x%pixWidth==0)
					++xNew;
				if(vals[y][x]<=mean-stdDev*stdDevs)
				{
					//this pixel's value is far enough away from the mean value
					//that we'll consider it black.
					++retVal[yNew][xNew];
				}//end black if
			}//end x for
			xNew=-1; //reset xNew for the next row of the image.
		}//end y for
		
		//classify cells as either 0 or 1 based on whether half or more are 
		//black or white.  Ties fall to black.
		for(int y=0;y<retVal.length;++y)
		{
			for(int x=0;x<retVal[y].length;++x)
			{
				if(retVal[y][x]>=(pixWidth*pixHeight)/2.0)
					retVal[y][x]=1;
				else
					retVal[y][x]=0;
			}//end x for
		}//end y for
		
		return retVal;
	}

	public BufferedImage renderPattern(int[][] pattern, ProcessingParams params)
	{
		BufferedImage retVal=
			new BufferedImage(params.getPixWidth()*pattern[0].length,
					params.getPixHeight()*pattern.length,
					BufferedImage.TYPE_INT_RGB);
		Graphics2D g2d=(Graphics2D)retVal.getGraphics();
		
		//generate the image
		for(int y=0;y<pattern.length;++y)
		{
			for(int x=0;x<pattern[y].length;++x)
			{
				if(pattern[y][x]==1)
				{
					g2d.setPaint(Color.black);
				}
				else if(pattern[y][x]==0)
				{
					g2d.setPaint(Color.white);
				}
				else
				{
					//if it isn't a 0 or a 1, then we don't know how to render 
					//it so throw an argument exception.
					throw(new IllegalArgumentException(
							"Unknown pattern designator "+((Integer)pattern[y][x]).toString()));
				}
				
				g2d.fill(new Rectangle2D.Double(
						x*params.getPixHeight(),y*params.getPixHeight(),
						params.getPixWidth(),params.getPixHeight()));
			}//end x for
		}//end y for
	//	g2d.drawImage(retVal, null, 0, 0);
		
		return retVal;
	}

	public StatisticsReturnType getStatistics(BufferedImage image,
			StatisticsParams params)
	{
		ExtractBlackStatistics retVal=new ExtractBlackStatistics();
		int [] histogram= new int[256];
		ColorModel cm=image.getColorModel();
		int rgbComponents[]=new int[3];
		float hsbComponents[]=new float[3];
		double accum=0.0;
		double mean=0.0;
		double stdDev=0.0;
		double [][] vals=new double [image.getHeight()][image.getWidth()];
		
		//initialize the histogram
		for(int i=0;i<256;++i)
		{
			histogram[i]=0;
		}
		
	
		//compute the histogram of values
		for(int y=0;y<image.getHeight();++y)
		{
			for(int x=0;x<image.getWidth();++x)
			{
				cm.getComponents(image.getRGB(x, y), rgbComponents, 0);
				Color.RGBtoHSB(rgbComponents[0], rgbComponents[1], 
						rgbComponents[2], hsbComponents);
				++histogram[(int)(255*hsbComponents[2])];
				accum+=hsbComponents[2]; //used for the mean
				vals[y][x]=hsbComponents[2];
			}
		}
		
		mean=accum/(image.getWidth()*image.getHeight());
		retVal.setMean(mean);
		retVal.setHistogram(histogram);
		accum=0.0;
		
		//compute standard deviation.
		for(int y=0; y<image.getHeight();++y)
			for(int x=0; x<image.getWidth();++x)
			{
				accum+=(mean-vals[y][x])*(mean-vals[y][x]);
			}
		stdDev=Math.sqrt(accum/(image.getWidth()*image.getHeight()));
		retVal.setStdDev(stdDev);
		
		return retVal;
	}
}
